#include "epp_netlink.h"


struct sock *gl_nlsk = NULL;
#define NETLINK_TEST     31
#define PID_TEST 100
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32)
    #define EPP_NLMSG_DATA(nlhder) NLMSG_DATA(nlhder)
#else
    #define EPP_NLMSG_DATA(nlhder) nlmsg_data(nlhder)
#endif

int _nelk_recv(struct sk_buff *skb)
{
    struct nlmsghdr *nlhdr = NULL;
    char *msg = NULL;
    msg_data_t stdata;
    
    if(skb == NULL)
    {
        printk("Netlink recvive msg is null\n");
        return -1;
    }
    nlhdr = nlmsg_hdr(skb);
    
    msg = EPP_NLMSG_DATA(nlhdr);
    
    if (NLMSG_PAYLOAD(nlhdr, 0) != sizeof(msg_data_t))
    {
        printk("Netlink recvive msg is invalid\n");
        return -1;
    }
    memset(&stdata, 0, sizeof(stdata));
    memcpy(&stdata, msg, sizeof(stdata));
    set_test_file(&stdata);
    printk("receive filename is %s, permission is %s\n", stdata.szfilename, stdata.szpermission);
    
    return 0;
}

#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18))
void nelk_recv(struct sock *sk, int len)
{
    struct sk_buff *skb = NULL;
    int rc = 0;
re_receive:
    skb = skb_recv_datagram(sk, 0, 0, &rc);
    if(rc == -EINTR){
        goto re_receive;
    }
    if(rc < 0){
        printk("Netlink recvice message error\n");
        return;
    }
    if(_nelk_recv(skb) < 0){
        ;
    }
    kfree_skb(skb);
    return;
}
#else
void nelk_recv(struct sk_buff *skb)
{
    if( _nelk_recv(skb) < 0){
        ;
    }
    return ;
}
#endif

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32) 
int nelk_send(void *data, int datalen)
{
    struct sk_buff *skb = NULL;
    struct nlmsghdr *nlhdr = NULL;
    char *msg = NULL;
    size_t size = 0;
    int ret = 0;
    int re_count = 0;
    
    if(gl_nlsk == NULL)
    {
        printk("Netlink socket error\n");
        return -1;
    }
    size = (data && datalen) ? datalen:0;
retry: 
    skb = alloc_skb(NLMSG_SPACE(size), GFP_ATOMIC);
    if(skb == NULL)
	{
        printk("netlink send error: No enough memory\n");
        return -ENOMEM;
    }
    nlhdr = NLMSG_PUT(skb, 0, 0, NETLINK_TEST, size);
	if(!nlhdr)
		goto nlmsg_failure;
	
    if(datalen)
	{
        msg = (char *)(NLMSG_DATA(nlhdr));
        memcpy(msg, data, datalen);
    }
	
    ret = netlink_unicast(gl_nlsk, skb, PID_TEST, 0);
    if(ret < 0)
    {
        printk("netlink unicast send error");
    }
	
    return 0; 
nlmsg_failure:
    kfree_skb(skb);
    return ret;
}
#else
int nelk_send(void *data, int datalen)
{
    int ret = 0;
    struct sk_buff *skb = NULL;
    struct nlmsghdr *nlh = NULL;

    skb = nlmsg_new(datalen, GFP_ATOMIC);
    if (!skb)
        return -ENOMEM;
    nlh = nlmsg_put(skb, 0, 0, NETLINK_TEST, datalen, 0);
    if (!nlh)
        goto out_kfree_skb;
    memcpy(nlmsg_data(nlh), data, datalen);
    
    ret = nlmsg_unicast(gl_nlsk, skb, PID_TEST);
    if(ret < 0)
	{
        printk("netlink unicast send error");
    }
    return 0;

out_kfree_skb:
    kfree_skb(skb);
    return -1;
}
#endif

#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18))
static struct sock * create_kernel_netlink(int unit, void (*input)(struct sock*sk,int len))
#else
static struct sock * create_kernel_netlink(int unit, void (*input)(struct sk_buff *skb))
#endif
{
    struct sock *ns = NULL;
    
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18))
    ns = netlink_kernel_create(unit, 0, input, THIS_MODULE); 
#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && (LINUX_VERSION_CODE <= KERNEL_VERSION(3,5,7)) 
    ns = netlink_kernel_create(&init_net, unit, 0, input, NULL, THIS_MODULE); 
#else
    {
        struct netlink_kernel_cfg nelkcfg;
        memset(&nelkcfg, 0x00, sizeof(nelkcfg));
        nelkcfg.input = input;
        ns = netlink_kernel_create(&init_net, unit, &nelkcfg); 
    }
#endif

    return ns;
}

int nelk_init(void)
{
    gl_nlsk = create_kernel_netlink(NETLINK_TEST,nelk_recv);
    if(gl_nlsk == NULL)
    {
        printk("create kernel netlink error\n");
        return -1;
    }
    
    gl_nlsk->sk_sndtimeo = 10; //set send timeout
    
    return 0;
}
void nelk_release(void)
{
    if(gl_nlsk)
    {
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18))
        sock_release(gl_nlsk->sk_socket);
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32))
        netlink_kernel_release(gl_nlsk);
#endif
    }
}
